-
Notifications
You must be signed in to change notification settings - Fork 49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add sigstore attest
CLI subcommand to sign using DSSE envelopes
#1115
Add sigstore attest
CLI subcommand to sign using DSSE envelopes
#1115
Conversation
898c04c
to
ef85d00
Compare
/gcbrun |
sigstore/_cli.py
Outdated
dsse_options.add_argument( | ||
"--predicate-type", | ||
metavar="TYPE", | ||
choices=PREDICATE_TYPES_CLI_MAP, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity: do other clients use these shorthand names? I'm wondering if we should just use the predicate URLs directly as choices here -- that makes the CLI slightly less ergonomic, but avoids adding one more custom convention 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I got these from cosign
:
--type='custom':
specify a predicate type
(slsaprovenance|slsaprovenance02|slsaprovenance1|link|spdx|spdxjson|cyclonedx|vuln|openvex|custom)
or an URI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah my bad, I forgot I had changed them. I changed them to slsaprovenance0_2
and slsaprovenance1_0
because slsaprovenance1
seems ambiguous. But yeah, these are not standard in any way. We can switch to the URLs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! Makes perfect sense to use them, then.
Edit: Or maybe not, given that the current names are somewhat ambiguous. Let's go with URLs only for now, and we can always move to support shorthand names once clients unify on them.
CC @loosebazooka @haydentherapper for visibility -- this is maybe too much of an implementation detail to standardize, but something for other clients to be aware of 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems reasonable to me to specify the predicate type as its full URL and not deal with shorthand.
sigstore/_cli.py
Outdated
output_options = attest.add_argument_group("Output options") | ||
output_options.add_argument( | ||
"--no-default-files", | ||
action="store_true", | ||
default=_boolify_env("SIGSTORE_NO_DEFAULT_FILES"), | ||
help="Don't emit the default output files ({input}.sigstore.json)", | ||
) | ||
output_options.add_argument( | ||
"--signature", | ||
"--output-signature", | ||
metavar="FILE", | ||
type=Path, | ||
default=os.getenv("SIGSTORE_OUTPUT_SIGNATURE"), | ||
help=( | ||
"Write a single signature to the given file; does not work with multiple input files" | ||
), | ||
) | ||
output_options.add_argument( | ||
"--certificate", | ||
"--output-certificate", | ||
metavar="FILE", | ||
type=Path, | ||
default=os.getenv("SIGSTORE_OUTPUT_CERTIFICATE"), | ||
help=( | ||
"Write a single certificate to the given file; does not work with multiple input files" | ||
), | ||
) | ||
output_options.add_argument( | ||
"--bundle", | ||
metavar="FILE", | ||
type=Path, | ||
default=os.getenv("SIGSTORE_BUNDLE"), | ||
help=( | ||
"Write a single Sigstore bundle to the given file; does not work with multiple input " | ||
"files" | ||
), | ||
) | ||
output_options.add_argument( | ||
"--output-directory", | ||
metavar="DIR", | ||
type=Path, | ||
default=os.getenv("SIGSTORE_OUTPUT_DIRECTORY"), | ||
help=( | ||
"Write default outputs to the given directory (conflicts with --signature, --certificate" | ||
", --bundle)" | ||
), | ||
) | ||
output_options.add_argument( | ||
"--overwrite", | ||
action="store_true", | ||
default=_boolify_env("SIGSTORE_OVERWRITE"), | ||
help="Overwrite preexisting signature and certificate outputs, if present", | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that this is a brand new command, I'm kind of tempted to remove/avoid the need to do anything except emit bundles 🙂 -- I think the only options we need here are --bundle
and --overwrite
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The advantage of having these is that it allows us to reuse most of the code that was already written for sigstore sign
. But I can remove them and refactor things around a bit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this still needs to be done -- IMO it's okay if end up being able to create a reusable _sign_common
, since these extra flags are about the same size as the helper anyays (and we don't want to support them).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed! See last commit
sigstore/dsse/_predicate.py
Outdated
|
||
uri: Optional[StrictStr] | ||
digest: Optional[DigestSetSource] | ||
entry_point: Optional[StrictStr] = Field(None, alias="entryPoint") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: Rather than having to explicitly declare each alias
here and below, maybe we can define a common base model with a model_config
that tweaks alias generation?
Something like:
from pydantic.config import ConfigDict
from pydantic.alias_generators import to_camel
class _PredicateBase(BaseModel):
model_config = ConfigDict(alias_generator=to_camel)
class ConfigSource(_PredicateBase):
...
...I think something like that will work, as will save us some manual typing 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea! I also fixed a couple of bugs I found:
slsa-framework/slsa-github-generator
incorrectly generates predicates usingbuildInvocationID
(note the final uppercase D, should bebuildInvocationId
instead). In order to fix that, I added a validation alias that allows for both options. The serialization alias will use the camelCase one set by the config you suggested.- I was dumping the model without specifying it should output the camelCase aliases rather than our snake_case field names. Fixed by passing
by_alias=True
tomodel_dump()
- Our model dump also included the optional fields that were set to
None
. However, that's how we model them, those fields are just absent in the source predicate. So now we passexclude_none=True
tomodel_dump
, to exclude those fields from the dump.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mmm given the last point, I wonder if instead of dumping our Predicate model, we should instead just validate it and then use the exact input the user gave us to build the in-toto statement.
Otherwise we might end up modifying it when doing the JSON -> Model -> JSON roundtrip (for example, the original JSON uses ID
but the final JSON uses Id
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@woodruffw WDYT about this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@facutuesca I think that's a good idea! In principle the each JSON layout shouldn't need to be stable, but in practice users might expect that (and as you've pointed out it avoids a potential source of ser/de errors on our side).
5684646
to
48051b9
Compare
/gcbrun |
/gcbrun |
Signed-off-by: Facundo Tuesca <[email protected]>
Signed-off-by: Facundo Tuesca <[email protected]>
Signed-off-by: Facundo Tuesca <[email protected]>
Signed-off-by: Facundo Tuesca <[email protected]>
c4abdf5
to
b6cedd0
Compare
Signed-off-by: Facundo Tuesca <[email protected]>
b6cedd0
to
344cdd8
Compare
/gcbrun |
Signed-off-by: Facundo Tuesca <[email protected]>
08f172c
to
fd7e9e2
Compare
@@ -179,6 +180,58 @@ Output options: | |||
``` | |||
<!-- @end-sigstore-sign-help@ --> | |||
|
|||
|
|||
### Signing with DSSE envelopes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thinking out loud: we should probably add some documentation about when users might want to do this versus "normal" signing. But we can do that in a follow-up 🙂
PREDICATE_TYPE_SLSA_v0_2 = "https://slsa.dev/provenance/v0.2" | ||
PREDICATE_TYPE_SLSA_v1_0 = "https://slsa.dev/provenance/v1" | ||
|
||
SUPPORTED_PREDICATE_TYPES = [PREDICATE_TYPE_SLSA_v0_2, PREDICATE_TYPE_SLSA_v1_0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flagging for a follow-up: maybe we can make this an enum.Enum
instead? That will reduce the number of unique imports we need to do, and allow us to define "supported" as "present in the enum".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @facutuesca, LGTM! I've flagged two things for follow-ups 🙂
/gcbrun |
Summary
Part of #1111.
Adds a
sigstore attest
CLI subcommand to sign using DSSE. The command is very similar tosigstore sign
, but it takes two new options:predicate-type
: One of[ https://slsa.dev/provenance/v0.2, https://slsa.dev/provenance/v1 ]
. The predicate type for the in-toto statement being signed over.predicate
: The path to the file containing the predicate JSON. The predicate's contents must match the predicate type specified inpredicate-type
.Since the logic for both
sigstore sign
andsigstore attest
is mostly the same, this PR extracts that shared logic into a separate function_sign_common()
, which is then called by_sign()
and_attest()
in order to not duplicate that logic.In order to test it, I have uploaded a SLSA v0.2 provenance predicate here, originally generated by GitHub for this
sigstore-python
release.Once downloaded, it can be used to sign over a file with the following command:
Release Note
sigstore attest
subcommand has been added. This command issimilar to
cosign attest
in that it signs over an artifact and apredicate using a DSSE envelope. This commands requires the user to pass
a path to the file containing the predicate, and the predicate type.
Currently only the SLSA Provenance v0.2 and v1.0 types are supported.
Documentation
Updated the README
cc @woodruffw